#include <fstream>
#include "binary.h"

static const char pad = '=';

static const char alphabet64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static const char table64[] =
{
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
    -1, -1, -1, -1, -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
    29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
    49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};

int base64_encode(const unsigned char* inbuf, int inlen, char* outbuf, int outlen)
{
    int in = 0;
    int out = 0;

    while(in < inlen)
    {
        char c = (inbuf[in]>>2) & 0x3f;
        if(out < outlen)
            outbuf[out++] = alphabet64[c];
        else return -1;

        c = (inbuf[in]<<4) & 0x3f;
        if(++in < inlen)
            c |= (inbuf[in]>>4) & 0x0f;
        if(out < outlen)
            outbuf[out++] = alphabet64[c];
        else return -1;

        if(in < inlen)
        {
            c = (inbuf[in]<<2) & 0x3c;
            if(++in < inlen)
                c |= (inbuf[in]>>6) & 0x03;
            if(out < outlen)
                outbuf[out++] = alphabet64[c];
            else
                return -1;
        }
        else
        {
            c = pad;
            if(out < outlen)
                outbuf[out++] = c;
            else
                return -1;
        }

        if(in < inlen)
        {
            c = inbuf[in++] & 0x3f;
            if(out < outlen)
                outbuf[out++] = alphabet64[c];
            else
                return -1;
        }
        else
        {
            c = pad;
            if(out < outlen)
                outbuf[out++] = c;
            else
                return -1;
        }
    }

    outbuf[out] = '\0';

    return out;
}

int base64_decode(const unsigned char* inbuf, int inlen, char* outbuf, int outlen)
{
    int in = 0;
    int out = 0;

    while(in < inlen)
    {
        char c,d;

        c = table64[inbuf[in]]; ++in;
        if(in < inlen)
        {
            d = table64[inbuf[in]]; ++in;
            c = (c<<2) | ((d>>4)&0x3);
            if(out < outlen)
                outbuf[out++] = c;
            else
                return -1;
        }

        if(in < inlen)
        {
            c = inbuf[in];
            if(pad == c) break;
            c = table64[inbuf[in]]; ++in;
            d = ((d<<4)&0xf0) | ((c>>2)&0xf);
            if(out < outlen)
                outbuf[out++] = d;
            else
                return -1;
        }

        if(in < inlen)
        {
            d = inbuf[in];
            if(pad == d) break;
            d = table64[inbuf[in]]; ++in;
            c = ((c<<6)&0xc0) | d;
            if(out < outlen)
                outbuf[out++] = c;
            else
                return -1;
        }
    }

    outbuf[out] = '\0';

    return out;
}

static const char to_hex_table[256][2] =
{ 
    {'0','0'}, {'0','1'}, {'0','2'}, {'0','3'}, {'0','4'}, {'0','5'}, {'0','6'}, {'0','7'}, {'0','8'}, {'0','9'}, {'0','A'}, {'0','B'}, {'0','C'}, {'0','D'}, {'0','E'},  {'0','F'},
    {'1','0'}, {'1','1'}, {'1','2'}, {'1','3'}, {'1','4'}, {'1','5'}, {'1','6'}, {'1','7'}, {'1','8'}, {'1','9'}, {'1','A'}, {'1','B'}, {'1','C'}, {'1','D'}, {'1','E'},  {'1','F'},
    {'2','0'}, {'2','1'}, {'2','2'}, {'2','3'}, {'2','4'}, {'2','5'}, {'2','6'}, {'2','7'}, {'2','8'}, {'2','9'}, {'2','A'}, {'2','B'}, {'2','C'}, {'2','D'}, {'2','E'},  {'2','F'},
    {'3','0'}, {'3','1'}, {'3','2'}, {'3','3'}, {'3','4'}, {'3','5'}, {'3','6'}, {'3','7'}, {'3','8'}, {'3','9'}, {'3','A'}, {'3','B'}, {'3','C'}, {'3','D'}, {'3','E'},  {'3','F'},
    {'4','0'}, {'4','1'}, {'4','2'}, {'4','3'}, {'4','4'}, {'4','5'}, {'4','6'}, {'4','7'}, {'4','8'}, {'4','9'}, {'4','A'}, {'4','B'}, {'4','C'}, {'4','D'}, {'4','E'},  {'4','F'},
    {'5','0'}, {'5','1'}, {'5','2'}, {'5','3'}, {'5','4'}, {'5','5'}, {'5','6'}, {'5','7'}, {'5','8'}, {'5','9'}, {'5','A'}, {'5','B'}, {'5','C'}, {'5','D'}, {'5','E'},  {'5','F'},
    {'6','0'}, {'6','1'}, {'6','2'}, {'6','3'}, {'6','4'}, {'6','5'}, {'6','6'}, {'6','7'}, {'6','8'}, {'6','9'}, {'6','A'}, {'6','B'}, {'6','C'}, {'6','D'}, {'6','E'},  {'6','F'},
    {'7','0'}, {'7','1'}, {'7','2'}, {'7','3'}, {'7','4'}, {'7','5'}, {'7','6'}, {'7','7'}, {'7','8'}, {'7','9'}, {'7','A'}, {'7','B'}, {'7','C'}, {'7','D'}, {'7','E'},  {'7','F'},
    {'8','0'}, {'8','1'}, {'8','2'}, {'8','3'}, {'8','4'}, {'8','5'}, {'8','6'}, {'8','7'}, {'8','8'}, {'8','9'}, {'8','A'}, {'8','B'}, {'8','C'}, {'8','D'}, {'8','E'},  {'8','F'},
    {'9','0'}, {'9','1'}, {'9','2'}, {'9','3'}, {'9','4'}, {'9','5'}, {'9','6'}, {'9','7'}, {'9','8'}, {'9','9'}, {'9','A'}, {'9','B'}, {'9','C'}, {'9','D'}, {'9','E'},  {'9','F'},
    {'A','0'}, {'A','1'}, {'A','2'}, {'A','3'}, {'A','4'}, {'A','5'}, {'A','6'}, {'A','7'}, {'A','8'}, {'A','9'}, {'A','A'}, {'A','B'}, {'A','C'}, {'A','D'}, {'A','E'},  {'A','F'},
    {'B','0'}, {'B','1'}, {'B','2'}, {'B','3'}, {'B','4'}, {'B','5'}, {'B','6'}, {'B','7'}, {'B','8'}, {'B','9'}, {'B','A'}, {'B','B'}, {'B','C'}, {'B','D'}, {'B','E'},  {'B','F'},
    {'C','0'}, {'C','1'}, {'C','2'}, {'C','3'}, {'C','4'}, {'C','5'}, {'C','6'}, {'C','7'}, {'C','8'}, {'C','9'}, {'C','A'}, {'C','B'}, {'C','C'}, {'C','D'}, {'C','E'},  {'C','F'},
    {'D','0'}, {'D','1'}, {'D','2'}, {'D','3'}, {'D','4'}, {'D','5'}, {'D','6'}, {'D','7'}, {'D','8'}, {'D','9'}, {'D','A'}, {'D','B'}, {'D','C'}, {'D','D'}, {'D','E'},  {'D','F'},
    {'E','0'}, {'E','1'}, {'E','2'}, {'E','3'}, {'E','4'}, {'E','5'}, {'E','6'}, {'E','7'}, {'E','8'}, {'E','9'}, {'E','A'}, {'E','B'}, {'E','C'}, {'E','D'}, {'E','E'},  {'E','F'},
    {'F','0'}, {'F','1'}, {'F','2'}, {'F','3'}, {'F','4'}, {'F','5'}, {'F','6'}, {'F','7'}, {'F','8'}, {'F','9'}, {'F','A'}, {'F','B'}, {'F','C'}, {'F','D'}, {'F','E'},  {'F','F'},

};

int hexify_encode(const unsigned char* inbuf, int inlen, char* outbuf, int outlen)
{
    int in = 0;
    int out = 0;

    while(in < inlen)
    {
        if(out < outlen)
            outbuf[out++] = to_hex_table[inbuf[in]][0];
        else
            return -1;

        if(out < outlen)
            outbuf[out++] = to_hex_table[inbuf[in++]][1];
        else
            return -1;
    }

    return out;
}

static char getHexDigitVal(const char c)
{
    // c is a digit from '0' to '9'
    if(c >= 48 && c <= 57)
    {
        return c - 48;
    }
    // c is an uppercase letter from 'A' to 'F'
    else if(c >= 65 && c <= 70)
    {
        // result = c - 65 + 10
        return c - 55;
    }

    return -1;
}

int hexify_decode(const unsigned char* inbuf, int inlen, char* outbuf, int outlen)
{
    if(inlen % 2 != 0)
        return -2;

    int in = 0;
    int out = 0;

    while(in < inlen)
    {
        char c0 = getHexDigitVal(inbuf[in++]);
        char c1 = getHexDigitVal(inbuf[in++]);

        if(out < outlen)
            outbuf[out++] = (c0<<4) | c1;
        else
            return -1;
    }

    return out;
}

int file_size(char* filename)
{
    int length = -1;
    std::ifstream is(filename, std::ios::binary);

    if(is)
    {
        is.seekg (0, is.end);
        length = is.tellg();
        is.close();
    }

    return length;
}

bool file_read(char* filename, char* outbuf, int outlen)
{
    std::ifstream is(filename, std::ios::binary);

    if(is)
    {
        is.read(outbuf, outlen);
        is.close();
        return true;
    }

    return false;
}

bool file_write(char* filename, const char* inbuf, int inlen, bool isapp)
{
    std::ios_base::openmode mode = std::ios::binary;
    
    if(isapp) mode |= std::ios_base::app;

    std::ofstream os(filename, mode);
    
    if(os)
    {
        os.write(inbuf, inlen);
        os.flush(); 
        os.close();
        return true;
    }

    return false;
}

//UCS                 UTF-8 
//00000000 - 0000007F 0xxxxxxx 
//00000080 - 000007FF 110xxxxx 10xxxxxx 
//00000800 - 0000FFFF 1110xxxx 10xxxxxx 10xxxxxx 

//00010000 - 001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 
//00200000 - 03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 
//04000000 - 7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 

#define	MAKEBITS         0x3F
#define MAKEBYTE         0x80
#define MAKE2BYTES       0xC0
#define MAKE3BYTES       0xE0
#define MAKE4BYTES       0xF0
#define MAKE5BYTES       0xF8
#define MAKE6BYTES       0xFC

#define TO_UTF8(x)	(static_cast<unsigned char>(x))

int split_utf8(const unsigned char* inbuf, int inlen, int howmany)
{
    int length = 0;

    for(int i = 0; i<howmany&&length<inlen; ++i)
    {
        // 1110xxxx 10xxxxxx 10xxxxxx
        if(MAKE3BYTES == (inbuf[length] & MAKE3BYTES))
        {
            length += 3;
        }
        // 110xxxxx 10xxxxxx
        else if(MAKE2BYTES == (inbuf[length] & MAKE2BYTES))
        {
            length += 2;
        }
        // 0xxxxxxx
        else if(inbuf[length] < MAKEBYTE)
        {
            ++length;
        }
    }

    return length;
}

int ucs2_utf8(const unsigned short* inbuf, int inlen, unsigned char* outbuf, int outlen)
{
    int length = 0;

	for(int i = 0; i < inlen; ++i)
	{
		// 0xxxxxxx
		if(inbuf[i] < 0x80)
		{
            if(length + 1 > outlen) return -1;

            outbuf[length++] = TO_UTF8(inbuf[i]);
		}
		// 110xxxxx 10xxxxxx
		else if(inbuf[i] < 0x800)
		{
            if(length + 2 > outlen) return -2;

			outbuf[length++] = MAKE2BYTES | TO_UTF8(inbuf[i] >> 6);
			outbuf[length++] = MAKEBYTE | TO_UTF8(inbuf[i] & MAKEBITS);
		}
		// 1110xxxx 10xxxxxx 10xxxxxx
		else
		{
            if(length + 3 > outlen) return -3;

			outbuf[length++] = MAKE3BYTES | TO_UTF8(inbuf[i] >> 12);
			outbuf[length++] = MAKEBYTE | TO_UTF8((inbuf[i] >> 6) & MAKEBITS);
			outbuf[length++] = MAKEBYTE | TO_UTF8(inbuf[i] & MAKEBITS);
		}
	}

    return length;
}

int utf8_ucs2(const unsigned char* inbuf, int inlen, unsigned short* outbuf, int outlen)
{
    int length = 0;

	for(int i = 0; i < inlen;)
	{
		unsigned short ch;

		// 1110xxxx 10xxxxxx 10xxxxxx
		if(MAKE3BYTES == (inbuf[i] & MAKE3BYTES))
		{
			ch = ((inbuf[i] & 0x0F) << 12) | ((inbuf[i+1] & MAKEBITS) << 6) | (inbuf[i+2] & MAKEBITS); i += 3;
		}
		// 110xxxxx 10xxxxxx
		else if(MAKE2BYTES == (inbuf[i] & MAKE2BYTES))
		{
			ch = ((inbuf[i] & 0x1F) << 6) | (inbuf[i+1] & MAKEBITS); i += 2;
		}
		// 0xxxxxxx
		else if(inbuf[i] < MAKEBYTE)
		{
			ch = inbuf[i]; i += 1;
		}

        if(length + 1 > outlen)
            return -1;
        else
            outbuf[length++] = ch;
	}

    return length;
}

//#include <iconv.h>
//int code_convert(char *from_charset, char *to_charset, char *inbuf, int inlen, char *outbuf, int &outlen)
//{
//    if(NULL == inbuf || NULL == outbuf)
//        return -1;
//
//    iconv_t cd = iconv_open(to_charset, from_charset);
//    if(cd <= 0)
//        return -1;
//   
//    int temp = outlen;
//    char **pin = &inbuf;
//    char **pout = &outbuf;
//    memset(outbuf, 0, outlen);
//    if((int)iconv(cd, pin, (size_t*)&inlen, pout,(size_t*)&outlen) == -1)
//    {
//        iconv_close(cd);
//        return -1;
//    }
//
//    iconv_close(cd);
//    outlen = temp - outlen;
//    return 0;
//}
//
//char* g2u(char *inbuf, int inlen, char *outbuf, int outlen)
//{
//    if(code_convert("GBK", "UTF-8", inbuf, inlen, outbuf, outlen) != 0)
//        return NULL;
//    else
//        return outbuf;
//}
//
//char* ucs2u(char *inbuf, int inlen, char *outbuf, int outlen)
//{
//    if(code_convert("UCS-2BE", "UTF-8", inbuf, inlen, outbuf, outlen) !=0)
//        return NULL;
//    else
//        return outbuf;
//}
